package com.ggb.reactive.controller.admin

import com.ggb.reactive.constant.OrderStatus
import com.ggb.reactive.domain.vo.*
import com.ggb.reactive.exception.BaseException
import com.ggb.reactive.model.WebResult
import com.ggb.reactive.service.OrderService
import com.ggb.reactive.service.UserService
import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.tags.Tag
import lombok.RequiredArgsConstructor
import org.apache.poi.xssf.usermodel.XSSFWorkbook
import org.springframework.format.annotation.DateTimeFormat
import org.springframework.http.server.reactive.ServerHttpResponse
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
import reactor.core.scheduler.Schedulers
import java.io.ByteArrayOutputStream
import java.io.InputStream
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.LocalTime

@RestController
@RequestMapping("/admin/report")
@RequiredArgsConstructor
@Tag(name = "B端-报表管理")
class ReportController(
    val orderService: OrderService,
    val userService: UserService,

    ) {

    @Operation(summary = "营业额统计")
    @GetMapping("/turnoverStatistics")
    fun turnoverStatistics(
        @DateTimeFormat(pattern = "yyyy-MM-dd") begin: LocalDate,
        @DateTimeFormat(pattern = "yyyy-MM-dd") end: LocalDate
    ): Mono<WebResult<TurnoverReportVO>> {
        return getDataList(begin, end)
            .flatMap { date ->
                val beginDateTime = LocalDateTime.of(date, LocalTime.MIN)
                val endDateTime = LocalDateTime.of(date, LocalTime.MAX)
                orderService.sumByMap(OrderStatus.CANCELLED.getStatus(), beginDateTime, endDateTime)
                    .map { sum -> Pair(date, sum) }
            }
            .collectList()
            .map { pairs ->
                val dateList = pairs.joinToString(",") { it.first.toString() }
                val turnoverList = pairs.joinToString(",") { it.second.toString() }
                WebResult.success(TurnoverReportVO(dateList, turnoverList))
            }
    }

    @Operation(summary = "用户统计")
    @GetMapping("/userStatistics")
    fun userStatistics(
        @DateTimeFormat(pattern = "yyyy-MM-dd") begin: LocalDate,
        @DateTimeFormat(pattern = "yyyy-MM-dd") end: LocalDate
    ): Mono<WebResult<UserReportVO>> {
        return getDataList(begin, end)
            .flatMap { date ->
                val beginDateTime = LocalDateTime.of(date, LocalTime.MIN)
                val endDateTime = LocalDateTime.of(date, LocalTime.MAX)
                userService.countByMap(beginDateTime, endDateTime)
                    .zipWith(userService.countByMap(beginDateTime, endDateTime))
                    .map { tuple ->
                        val total = tuple.t1
                        val new = tuple.t2
                        UserStatisticsData(date, total, new)
                    }
            }
            .collectList()
            .map { dataList ->
                val dateList = dataList.joinToString(",") { it.date.toString() }
                val totalUserList = dataList.joinToString(",") { it.total.toString() }
                val newUserList = dataList.joinToString(",") { it.newUser.toString() }
                WebResult.success(UserReportVO(dateList, totalUserList, newUserList))
            }
    }

    @Operation(summary = "订单统计")
    @GetMapping("/ordersStatistics")
    fun ordersStatistics(
        @DateTimeFormat(pattern = "yyyy-MM-dd") begin: LocalDate,
        @DateTimeFormat(pattern = "yyyy-MM-dd") end: LocalDate
    ): Mono<WebResult<OrderReportVO>> {
        return getDataList(begin, end)
            .flatMap { date ->
                val beginDateTime = LocalDateTime.of(date, LocalTime.MIN)
                val endDateTime = LocalDateTime.of(date, LocalTime.MAX)
                orderService.countByMap(null, beginDateTime, endDateTime)
                    .zipWith(orderService.countByMap(OrderStatus.COMPLETED.getStatus(), beginDateTime, endDateTime))
                    .map { tuple ->
                        val total = tuple.t1
                        val validate = tuple.t2
                        OrderStatisticsData(date, total, validate)
                    }
            }
            .collectList()
            .map { data ->
                val dateList = data.joinToString(",") { it.date.toString() }
                val orderCountList = data.joinToString(",") { it.orderCount.toString() }
                val validOrderCountList = data.joinToString(",") { it.validOrderCount.toString() }

                val totalOrderCount = data.sumOf { it.orderCount }
                val validOrderCount = data.sumOf { it.validOrderCount }
                val validOrderRate = if (totalOrderCount != 0) {
                    validOrderCount.toDouble() / totalOrderCount.toDouble()
                } else {
                    0.0
                }
                WebResult.success(
                    OrderReportVO(
                        dateList,
                        orderCountList,
                        validOrderCountList,
                        totalOrderCount,
                        validOrderCount,
                        validOrderRate
                    )
                )
            }
    }

    @Operation(summary = "销量排名")
    @GetMapping("/top10")
    fun top10(
        @DateTimeFormat(pattern = "yyyy-MM-dd") begin: LocalDate,
        @DateTimeFormat(pattern = "yyyy-MM-dd") end: LocalDate
    ): Mono<WebResult<SalesTop10ReportVO>> {
        val beginTime = LocalDateTime.of(begin, LocalTime.MIN)
        val endTime = LocalDateTime.of(end, LocalTime.MAX)
        return orderService.getSalesTop10(beginTime, endTime)
            .collectList()
            .map { list ->
                val nameList = list.joinToString(",") { it.name.toString() }
                val numberList = list.joinToString(",") { it.number.toString() }
                WebResult.success(SalesTop10ReportVO(nameList, numberList))
            }
    }


    @Operation(summary = "导出报表")
    @GetMapping("/export")
    fun exportBusinessData(response: ServerHttpResponse): Mono<Void> {
        val dateBegin = LocalDate.now().minusDays(30)
        val dateEnd = LocalDate.now().minusDays(1)
        val begin = LocalDateTime.of(dateBegin, LocalTime.MIN)
        val end = LocalDateTime.of(dateEnd, LocalTime.MAX)

        return getBusinessData(begin, end).publishOn(Schedulers.boundedElastic()).publishOn(Schedulers.boundedElastic())
            .publishOn(Schedulers.boundedElastic())
            .flatMap { businessData ->
                val inputStream: InputStream? =
                    this.javaClass.classLoader.getResourceAsStream("template/运营数据报表模板.xlsx")
                val excel = XSSFWorkbook(inputStream)
                val sheet = excel.getSheet("Sheet1")
                sheet.getRow(1).getCell(1).setCellValue("时间：$dateBegin 至 $dateEnd")
                var row = sheet.getRow(3)
                row.getCell(2).setCellValue(businessData.turnover)
                row.getCell(4).setCellValue(businessData.orderCompletionRate)
                row.getCell(6).setCellValue(businessData.newUsers.toString())
                row = sheet.getRow(4)
                row.getCell(2).setCellValue(businessData.validOrderCount.toString())
                row.getCell(4).setCellValue(businessData.unitPrice)
                // 顺序处理30天的数据
                Flux.range(0, 30)
                    .concatMap { i ->
                        val date = dateBegin.plusDays(i.toLong())
                        getBusinessData(LocalDateTime.of(date, LocalTime.MIN), LocalDateTime.of(date, LocalTime.MAX))
                            .doOnNext { dayBusinessData ->
                                val xssfRow = sheet.getRow(7 + i)
                                xssfRow.getCell(1).setCellValue(date.toString())
                                xssfRow.getCell(2).setCellValue(dayBusinessData.turnover)
                                xssfRow.getCell(3).setCellValue(dayBusinessData.validOrderCount.toString())
                                xssfRow.getCell(4).setCellValue(dayBusinessData.orderCompletionRate)
                                xssfRow.getCell(5).setCellValue(dayBusinessData.unitPrice)
                                xssfRow.getCell(6).setCellValue(dayBusinessData.newUsers.toString())
                            }
                    }
                    .collectList()
                    .publishOn(Schedulers.boundedElastic())
                    .doOnNext {
                        val outputStream = ByteArrayOutputStream()
                        excel.write(outputStream)
                        excel.close()
                        response.writeWith(Mono.just(response.bufferFactory().wrap(outputStream.toByteArray())))
                            .subscribe()
                    }
            }
            .onErrorResume { e ->
                Mono.fromRunnable {
                    e.printStackTrace()
                }
            }.then()


    }


    /**
     * 根据开始时间和结束时间获取日期列表
     *
     * @param begin
     * @param end
     * @return
     */
    private fun getDataList(begin: LocalDate, end: LocalDate): Flux<LocalDate> {
        if (begin.isAfter(end)) {
            throw BaseException("开始日期不能大于结束日期")
        }
        return Flux.range(0, (end.toEpochDay() - begin.toEpochDay()).toInt() + 1)
            .map { begin.plusDays(it.toLong()) }
    }

    /**
     * 获取营业数据
     */
    private fun getBusinessData(begin: LocalDateTime, end: LocalDateTime): Mono<BusinessDataVO> {
        val orderCountMono = orderService.countByMap(null, begin, end)
        val turnoverMono = orderService.sumByMap(null, begin, end)
        val validOrderCountMono = orderService.countByMap(OrderStatus.COMPLETED.getStatus(), begin, end)
        val orderCompletionRateMono = orderService.countByMap(OrderStatus.COMPLETED.getStatus(), begin, end)
        val userCountMono = userService.countByMap(begin, end)

        return Mono.zip(
            orderCountMono,
            turnoverMono,
            validOrderCountMono,
            orderCompletionRateMono,
            userCountMono
        ).map { tuple ->
            val orderCount = tuple.t1
            val turnover = tuple.t2
            val validOrderCount = tuple.t3
            val orderCompletionRate = if (orderCount != 0) {
                tuple.t4.toDouble() / orderCount.toDouble()
            } else {
                0.0
            }
            val userCount = tuple.t5
            val unitPrice = if (validOrderCount != 0) {
                turnover / validOrderCount.toDouble()
            } else {
                0.0
            }
            BusinessDataVO(
                turnover = turnover,
                validOrderCount = validOrderCount,
                orderCompletionRate = orderCompletionRate,
                unitPrice = unitPrice,
                newUsers = userCount
            )
        }
    }


    data class UserStatisticsData(
        val date: LocalDate,
        val total: Int,
        val newUser: Int
    )

    data class OrderStatisticsData(
        val date: LocalDate,
        val orderCount: Int,
        val validOrderCount: Int
    )


}